﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Windows.Media.Effects;

namespace DeepZoomBookControl
{
    [TemplatePart(Name = "multiScaleImage", Type = typeof(MultiScaleImage))]
    [TemplatePart(Name = "shadow", Type = typeof(Rectangle))]
    [TemplatePart(Name = "contentBorder", Type = typeof(Border))]
    [TemplatePart(Name = "effect", Type = typeof(PageListEffect))]
    [TemplatePart(Name = "imgBrush", Type = typeof(ImageBrush))]

    public class DeepZoomBook : Control
    {
        #region Controls
        MultiScaleImage _msiBooks;
        Rectangle _shadow;
        Border _contentBorder;
        PageListEffect _effect;
        ImageBrush _imgBrush;
        #endregion
        //      <controls:PageListEffect x:Name="effect" PagingFactor="{Binding PagingFactor, ElementName=main}" x:Key="effect">
        //    <controls:PageListEffect.PrevState>
        //        <ImageBrush x:Name="imgBrush"/>
        //    </controls:PageListEffect.PrevState>
        //</controls:PageListEffect>
        private const double pageSize = 6;
        private readonly Point hideDestinationPoint = new Point(100, 100);

        public DeepZoomBook()
        {
            this.DefaultStyleKey = typeof(DeepZoomBook);
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _msiBooks = GetTemplateChild("multiScaleImage") as MultiScaleImage;
            _shadow = GetTemplateChild("shadow") as Rectangle;
            _contentBorder = GetTemplateChild("contentBorder") as Border;
            _effect = GetTemplateChild("effect") as PageListEffect;
            _imgBrush = GetTemplateChild("imgBrush") as ImageBrush;



            

            if (_msiBooks != null)
            {
                _msiBooks.ImageOpenSucceeded += msiBook_ImageOpenSucceeded;
                _msiBooks.MotionFinished += new RoutedEventHandler(_msiBooks_MotionFinished);

                this.MouseLeftButtonDown += new MouseButtonEventHandler(book_MouseLeftButtonDown);
                this.MouseLeftButtonUp += new MouseButtonEventHandler(book_MouseLeftButtonUp);
                this.MouseMove += new MouseEventHandler(book_MouseMove);
                this.MouseWheel += new MouseWheelEventHandler(book_MouseWheel);
                
                Zoom = 2;
            }

            if (_effect != null)
            {
                _effect.PagingFactor = this.PagingFactor;
            }

            CompositionTarget.Rendering += CompositionTarget_Rendering;
            Broadside = 1;
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (!(BroadsidesCount > 0))
                return;

            var shadowRect = new Rect();

            if (Broadside < 1)
            {
                shadowRect = GetSubImageRect(1);
                if (shadowRect.X < 0)
                    shadowRect = GetSubImageRect(3);

                if (Broadside > 0.5)
                {
                    var k = (Broadside - 0.5) * 2;
                    k = 1 - Math.Pow(1 - k, 4);
                    shadowRect.X -= shadowRect.Width * k;
                    shadowRect.Width *= (1 + k);
                }
            }
            else if (Broadside < BroadsidesCount - 1 || _msiBooks.SubImages.Count % 2 == 0)
            {
                shadowRect = GetSubImageRect(SelectedBroadside * 2);
                shadowRect.Width *= 2;
            }
            else
            {
                shadowRect = GetSubImageRect(SelectedBroadside);
            }

            _shadow.Width = shadowRect.Width;
            _shadow.Height = shadowRect.Height;
            _shadow.SetValue(Canvas.LeftProperty, shadowRect.X - 5);
            _shadow.SetValue(Canvas.TopProperty, shadowRect.Y + 5);

        }

        private Rect GetSubImageRect(int nSubImageIndex)
        {
            MultiScaleSubImage subImage = _msiBooks.SubImages[nSubImageIndex];
            Point topLeft = subImage.ViewportOrigin;
            topLeft.X = -(topLeft.X / subImage.ViewportWidth);
            topLeft.Y = -(topLeft.Y / subImage.ViewportWidth);
            double width = 1 / subImage.ViewportWidth;
            double height = width / subImage.AspectRatio;
            Point bottomright = new Point(topLeft.X + width, topLeft.Y + height);
            topLeft = _msiBooks.LogicalToElementPoint(topLeft);
            bottomright = _msiBooks.LogicalToElementPoint(bottomright);
            return new Rect(topLeft, bottomright);
        }
        #region Book source selection

        void msiBook_ImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            foreach (var page in _msiBooks.SubImages)
                page.ViewportOrigin = hideDestinationPoint;
            Broadside = 0;
            ChangeSelectedBroadside(0, 0);
        }

        private void ChangeBookSource(Uri uri)
        {
            _msiBooks.Source = new DeepZoomImageTileSource(uri);
        }

        #endregion

        #region Paging

        private void CreatePageScreenshot()
        {
            PagingFactor = 1;
            _imgBrush.ImageSource = new WriteableBitmap(_contentBorder, null);
        }

        private void FixPage(int newPage)
        {
            SelectedBroadside = newPage;
            PagingFactor = 1;
        }

        private bool ChangeSelectedBroadside(int oldPage, int newPage)
        {
            if (newPage > this.BroadsidesCount || newPage < 0)
                return false;

            if (newPage == 1 || oldPage == 0)
                _effect.K = 0;
            else
                _effect.ClearValue(PageListEffect.KProperty);

            foreach (var page in _msiBooks.SubImages.Skip(oldPage * 2).Take(2))
                page.ViewportOrigin = hideDestinationPoint;

            var selectedPages = _msiBooks.SubImages.Skip(newPage * 2).Take(2).ToArray();

            selectedPages[0].ViewportWidth = pageSize;

            selectedPages[0].ViewportOrigin = new Point(-pageSize / 2 + 1, -0.3);

            if (selectedPages.Length > 1)
            {
                selectedPages[1].ViewportWidth = 6;
                selectedPages[1].ViewportOrigin = new Point(-pageSize / 2, -0.3);
            }

            return true;
        }

        private bool isBroadsideAnimating = false;
        private bool ChangeBroadside(double oldBroadside, double newBroadside)
        {
            if (pagingDisabled)
                return false;

            var oldPage = (int)oldBroadside;
            var oldPagingFactor = oldBroadside - oldPage;

            var newPage = (int)newBroadside;
            var pagingFactor = newBroadside - newPage;

            if (newPage > this.BroadsidesCount || newPage < 0)
                return false;

            if (!isBroadsideAnimating)
            {
                AnimatePaging(newBroadside, oldBroadside);
                return false;
            }

            if (pagingFactor == 0)
            {
                this.FixPage(newPage);
                return true;
            }

            if (oldPage != newPage || oldPagingFactor == 0)
            {
                this.SelectedBroadside = newPage;
                this.CreatePageScreenshot();
                this.SelectedBroadside = newPage + 1;
            }

            this.PagingFactor = pagingFactor;

            return true;
        }

        private void AnimatePaging(double to, double? from = null)
        {
            if (to < 0 || to > BroadsidesCount || isBroadsideAnimating)
                return;

            isBroadsideAnimating = true;

            _contentBorder.Effect = _effect;

            DoubleAnimation anim = new DoubleAnimation()
            {
                From = from ?? this.Broadside,
                To = to,
                Duration = this.DefaultTransitionsTime
            };
            anim.Completed += (o, e) =>
            {
                isBroadsideAnimating = false;
                if (PagingFactor == 1)
                    pagingDisabled = false;
                _contentBorder.Effect = null;
            };

            _effect.Size = 1 / pageSize * Zoom;

            Storyboard s = new Storyboard();
            s.Children.Add(anim);
            Storyboard.SetTarget(anim,this);
            Storyboard.SetTargetProperty(anim, new PropertyPath("Broadside"));
            s.Begin();
        }

        public int BroadsidesCount { get { return _msiBooks.SubImages.Count / 2 - 1; } }

        #endregion

        private bool pagingDisabled = false;

        public UIElement PrintSource
        {
            get
            {
                return new Image() { Source = new WriteableBitmap(_contentBorder, null) };
            }
        }

        #region Zooming

        private bool ChangeZoom(double oldZoom, double newZoom)
        {
            if (isBroadsideAnimating)
                return false;

            if (newZoom < 1)
                return false;

            double zoomRatio = newZoom / oldZoom;
            pagingDisabled = true;
            var p = _msiBooks.ElementToLogicalPoint(new Point(_msiBooks.ActualWidth / 2, _msiBooks.ActualHeight / 2));

            _msiBooks.ZoomAboutLogicalPoint(zoomRatio, p.X, p.Y);

            return true;
        }

        private void book_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0) Zoom *= 1.1;
            else Zoom /= 1.1;
        }

        #endregion

        #region Move manipulations

        private bool isMouseLeftButtonPressed = false;
        private bool mouseMoved = false;
        private Point? prevMousePosition;
        private Point viewportOrigin;
        private void book_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isMouseLeftButtonPressed = true;
            prevMousePosition = e.GetPosition(this as UIElement);
            viewportOrigin = _msiBooks.ViewportOrigin;
            Focus();
        }

        private void book_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!mouseMoved)
            {
                if (e.GetPosition(this).X > _msiBooks.ActualWidth * (-_msiBooks.ViewportOrigin.X + 0.5) / _msiBooks.ViewportWidth)
                    Broadside++;
                else
                    Broadside--;
            }

            isMouseLeftButtonPressed = false;
            mouseMoved = false;
        }

        private void book_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isMouseLeftButtonPressed || isBroadsideAnimating)
                return;

            mouseMoved = true;

            Point targetPoint = e.GetPosition(this.Parent as UIElement);

            if (prevMousePosition != null)
            {
                var currPosition = e.GetPosition(_msiBooks);
                var currPositionRelative = _msiBooks.ElementToLogicalPoint(currPosition);
                var prevPositionRelative = _msiBooks.ElementToLogicalPoint(prevMousePosition.Value);

                viewportOrigin = new Point(viewportOrigin.X - (currPositionRelative.X - prevPositionRelative.X), viewportOrigin.Y - (currPositionRelative.Y - prevPositionRelative.Y));
                pagingDisabled = true;
                _msiBooks.ViewportOrigin = viewportOrigin;
                prevMousePosition = currPosition;
            }

            //prevMousePosition = e.GetPosition(this.Parent as UIElement);
            prevMousePosition = e.GetPosition(this as UIElement);
            _msiBooks.UpdateLayout();
            CorrectViewportOrigin();
        }

        private void CorrectViewportOrigin()
        {
            return;
            double leftBound = -0.3 / Zoom;
            double topBound = -0.3 / Zoom;
            double rightBound = 1.0 - 0.7 / Zoom;
            double bottomBound = 1.0 * _msiBooks.ActualHeight / _msiBooks.ActualWidth - 0.3 / Zoom;
            if (_msiBooks.ViewportOrigin.X < leftBound)
            {
                _msiBooks.ViewportOrigin = new Point(leftBound, _msiBooks.ViewportOrigin.Y);
            }
            if (_msiBooks.ViewportOrigin.Y < topBound)
            {
                _msiBooks.ViewportOrigin = new Point(_msiBooks.ViewportOrigin.X, -topBound);
            }
            if (_msiBooks.ViewportOrigin.X > rightBound)
            {
                _msiBooks.ViewportOrigin = new Point(rightBound, _msiBooks.ViewportOrigin.Y);
            }
            if (_msiBooks.ViewportOrigin.Y > bottomBound)
            {
                _msiBooks.ViewportOrigin = new Point(_msiBooks.ViewportOrigin.X, bottomBound);
            }
        }

        #endregion


        private void _msiBooks_MotionFinished(object sender, RoutedEventArgs e)
        {
            pagingDisabled = false;
            _effect.Center = (-_msiBooks.ViewportOrigin.X + 0.5) / _msiBooks.ViewportWidth;
        }



        #region DP declarations

        public static readonly DependencyProperty BookSourceProperty =
            DependencyProperty.Register("BookSource", typeof(Uri), typeof(DeepZoomBook), new PropertyMetadata(null, OnBookSourceChanged));

        public static readonly DependencyProperty AllowMouseZoomProperty =
            DependencyProperty.Register("AllowMouseZoom", typeof(bool), typeof(DeepZoomBook), new PropertyMetadata(true));

        public static readonly DependencyProperty ZoomProperty =
            DependencyProperty.Register("Zoom", typeof(double), typeof(DeepZoomBook), new PropertyMetadata(1.0, OnZoomChanged));

        public static readonly DependencyProperty PagingFactorProperty =
            DependencyProperty.Register("PagingFactor", typeof(double), typeof(DeepZoomBook), new PropertyMetadata(1.0));

        public static readonly DependencyProperty BroadsideProperty =
            DependencyProperty.Register("Broadside", typeof(double), typeof(DeepZoomBook), new PropertyMetadata(0.0, OnBroadsideChanged));

        public static readonly DependencyProperty SelectedBroadsideProperty =
            DependencyProperty.Register("SelectedBroadside", typeof(int), typeof(DeepZoomBook), new PropertyMetadata(0, OnSelectedBroadsideChanged));

        public static readonly DependencyProperty DefaultTransitionsTimeProperty =
            DependencyProperty.Register("DefaultTransitionsTime", typeof(TimeSpan), typeof(DeepZoomBook), new PropertyMetadata(new TimeSpan(0, 0, 1)));



        #endregion

        #region Behavior

        private static void OnBookSourceChanged(object target, DependencyPropertyChangedEventArgs e)
        {
            var book = target as DeepZoomBook;

            if (book == null)
                return;

            book.ChangeBookSource(e.NewValue as Uri);
        }

        private static void OnSelectedBroadsideChanged(object target, DependencyPropertyChangedEventArgs e)
        {
            var book = target as DeepZoomBook;

            if (book == null)
                return;

            if (!(e.NewValue is int))
                return;

            var token = DPCancelationToken.GetCancelationToken(book, e.Property);
            if (token.IsCancelling)
            {
                token.Finish();
                return;
            }

            if (book.ChangeSelectedBroadside((int)e.OldValue, (int)e.NewValue))
                return;

            token.Cancel(e.OldValue);
        }

        private static void OnBroadsideChanged(object target, DependencyPropertyChangedEventArgs e)
        {
            var book = target as DeepZoomBook;

            if (!(e.NewValue is double && e.OldValue is double))
                return;

            var newBroadside = (double)e.NewValue;

            var oldBroadside = (double)e.OldValue;

            var token = DPCancelationToken.GetCancelationToken(book, e.Property);
            if (token.IsCancelling)
            {
                token.Finish();
                return;
            }

            if (book.ChangeBroadside(oldBroadside, newBroadside))
                return;

            token.Cancel(e.OldValue);
        }

        private static void OnZoomChanged(object target, DependencyPropertyChangedEventArgs e)
        {
            var book = target as DeepZoomBook;

            if (book == null)
                return;

            if (!(e.NewValue is double && e.OldValue is double))
                return;

            var token = DPCancelationToken.GetCancelationToken(book, e.Property);
            if (token.IsCancelling)
            {
                token.Finish();
                return;
            }

            var oldZoom = (double)e.OldValue;
            var newZoom = (double)e.NewValue;

            if (book.ChangeZoom(oldZoom, newZoom))
                return;

            token.Cancel(e.OldValue);
        }

        #endregion

        #region Scalar properties

        public TimeSpan DefaultTransitionsTime
        {
            get { return (TimeSpan)GetValue(DefaultTransitionsTimeProperty); }
            set { SetValue(DefaultTransitionsTimeProperty, value); }
        }

        public double Broadside
        {
            get { return (double)GetValue(BroadsideProperty); }
            set { SetValue(BroadsideProperty, value); }
        }

        private int SelectedBroadside
        {
            get { return (int)GetValue(SelectedBroadsideProperty); }
            set { SetValue(SelectedBroadsideProperty, value); }
        }

        public double Zoom
        {
            get { return (double)GetValue(ZoomProperty); }
            set { SetValue(ZoomProperty, value); }
        }

        private double PagingFactor
        {
            get { return (double)GetValue(PagingFactorProperty); }
            set { SetValue(PagingFactorProperty, value); }
        }

        public Uri BookSource
        {
            get { return (Uri)GetValue(BookSourceProperty); }
            set { SetValue(BookSourceProperty, value); }
        }

        public bool AllowMouseZoom
        {
            get { return (bool)GetValue(AllowMouseZoomProperty); }
            set { SetValue(AllowMouseZoomProperty, value); }
        }

        #endregion



    }
}
